﻿using log4net;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using Velocity4Net.App.Event;
using Velocity4Net.Ctx;
using Velocity4Net.Runtime.Parser;
using Velocity4Net.Runtime.Parser.Node;
using Velocity4Net.Runtime.Resource;
using Velocity4Net.Util;
using Velocity4Net.Util.Introspection;

namespace Velocity4Net.Runtime
{

    /*
     * Licensed to the Apache Software Foundation (ASF) under one
     * or more contributor license agreements.  See the NOTICE file
     * distributed with this work for additional information
     * regarding copyright ownership.  The ASF licenses this file
     * to you under the Apache License, Version 2.0 (the
     * "License"); you may not use this file except in compliance
     * with the License.  You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing,
     * software distributed under the License is distributed on an
     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     * KIND, either express or implied.  See the License for the
     * specific language governing permissions and limitations
     * under the License.
     */


























































    /**
     * <p>This is the Runtime system for Velocity. It is the
     * single access point for all functionality in Velocity.
     * It adheres to the mediator pattern and is the only
     * structure that developers need to be familiar with
     * in order to get Velocity to perform.</p>
     *
     * <p>The Runtime will also cooperate with external
     * systems, which can make all needed setProperty() calls
     * before calling init().</p>
     * <pre>
     * -----------------------------------------------------------------------
     * N O T E S  O N  R U N T I M E  I N I T I A L I Z A T I O N
     * -----------------------------------------------------------------------
     * init()
     *
     * If init() is called by itself the RuntimeInstance will initialize
     * with a set of default values.
     * -----------------------------------------------------------------------
     * init(String/Properties)
     *
     * In this case the default velocity properties are layed down
     * first to provide a solid base, then any properties provided
     * in the given properties object will override the corresponding
     * default property.
     * -----------------------------------------------------------------------
     * </pre>
     *
     * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
     * @author <a href="mailto:jlb@houseofdistraction.com">Jeff Bowden</a>
     * @author <a href="mailto:geirm@optonline.net">Geir Magusson Jr.</a>
     * @version $Id: RuntimeInstance.java 1869271 2019-11-01 17:05:18Z cbrisson $
     */
    public class RuntimeInstance : RuntimeConstants, RuntimeServices
    {
        /**
         *  VelocimacroFactory object to manage VMs
         */
        private VelocimacroFactory vmFactory = null;

        /**
         * The Runtime logger.  The default instance is the "org.apache.velocity" logger.
         */
        private ILog log = LogManager.GetLogger(DEFAULT_RUNTIME_LOG_NAME);

        /**
         * The Runtime parser pool
         */
        private ParserPool parserPool;

        /**
         * Indicate whether the Runtime is in the midst of initialization.
         */
        private bool initializing = false;

        /**
         * Indicate whether the Runtime has been fully initialized.
         */
        private volatile bool initialized = false;

        /**
         * These are the properties that are laid down over top
         * of the default properties when requested.
         */
        private ExtProperties overridingProperties = null;

        /**
         * This is a hashtable of initialized directives.
         * The directives that populate this hashtable are
         * taken from the RUNTIME_DEFAULT_DIRECTIVES
         * property file.
         */
        private IDictionary runtimeDirectives = new Hashtable();
        /**
         * Copy of the actual runtimeDirectives that is shared between
         * parsers. Whenever directives are updated, the synchronized
         * runtimeDirectives is first updated and then an unsynchronized
         * copy of it is passed to parsers.
         */
        private IDictionary runtimeDirectivesShared;

        /**
         * Object that houses the configuration options for
         * the velocity runtime. The ExtProperties object allows
         * the convenient retrieval of a subset of properties.
         * For example all the properties for a resource loader
         * can be retrieved from the main ExtProperties object
         * using something like the following:
         *
         * ExtProperties loaderConfiguration =
         *         configuration.subset(loaderID);
         *
         * And a configuration is a lot more convenient to deal
         * with then conventional properties objects, or Maps.
         */
        private ExtProperties configuration = new ExtProperties();

        private ResourceManager resourceManager = null;

        /**
         * This stores the engine-wide set of event handlers.  Event handlers for
         * each specific merge are stored in the context.
         */
        private EventCartridge eventCartridge = null;

        /**
         * Whether to use string interning
         */
        private bool stringInterning = false;

        /**
         * Scope name for evaluate(...) calls.
         */
        private String evaluateScopeName = "evaluate";

        /**
         * Scope names for which to provide scope control objects in the context
         */
        private ISet<String> enabledScopeControls = new HashSet<String>();

        /**
         *  Opaque reference to something specified by the
         *  application for use in application supplied/specified
         *  pluggable components
         */
        private IDictionary applicationAttributes = null;

        /**
         *  Uberspector
         */
        private Uberspect uberSpect;

        /**
         * Default encoding
         */
        private String defaultEncoding;

        /**
         * Space gobbling mode
         */
        private SpaceGobbling spaceGobbling;

        /**
         * Whether hyphen is allowed in identifiers
         */
        private bool hyphenAllowedInIdentifiers;

        /**
         * The LogContext object used to track location in templates
         */
        private LogContext logContext;

        /**
         * Configured parser class
         * @since 2.2
         */
        private Constructor parserConstructor;

        /**
         * Configured replacement characters in parser grammar
         * @since 2.2
         */
        private ParserConfiguration parserConfiguration;

        /**
         * Creates a new RuntimeInstance object.
         */
        public RuntimeInstance()
        {
            reset();
        }

        /**
         * This is the primary initialization method in the Velocity
         * Runtime. The systems that are setup/initialized here are
         * as follows:
         *
         * <ul>
         *   <li>Logging System</li>
         *   <li>ResourceManager</li>
         *   <li>EventHandler</li>
         *   <li>Parser Pool</li>
         *   <li>Global Cache</li>
         *   <li>Static Content Include System</li>
         *   <li>Velocimacro System</li>
         * </ul>
         */
        public void init()
        {
            lock (this)
            {
                if (!initialized && !initializing)
                {
                    try
                    {
                        log.Debug("Initializing Velocity, Calling init()...");
                        initializing = true;

                        log.Trace("*****************************");
                        log.Debug("Starting Apache Velocity v" + VelocityEngineVersion.VERSION);
                        log.trace("RuntimeInstance initializing.");

                        initializeProperties();
                        initializeSelfProperties();
                        initializeLog();
                        initializeResourceManager();
                        initializeDirectives();
                        initializeEventHandlers();
                        initializeParserPool();

                        initializeIntrospection();
                        initializeScopeSettings();
                        /*
                         *  initialize the VM Factory.  It will use the properties
                         * accessible from Runtime, so keep this here at the end.
                         */
                        vmFactory.initVelocimacro();

                        log.trace("RuntimeInstance successfully initialized.");

                        initialized = true;
                        initializing = false;
                    }
                    catch (SystemException re)
                    {
                        // initialization failed at some point... try to reset everything
                        try
                        {
                            reset();
                        }
                        catch (SystemException re2) { } // prefer throwing the original exception
                        throw re;
                    }
                    finally
                    {
                        initializing = false;
                    }
                }
            }
        }

        /**
         * Resets the instance, so Velocity can be re-initialized again.
         *
         * @since 2.0.0
         */
        public void reset()
        {
            lock (this)
            {
                this.configuration = new ExtProperties();
                this.defaultEncoding = null;
                this.evaluateScopeName = "evaluate";
                this.eventCartridge = null;
                this.initialized = false;
                this.initializing = false;
                this.overridingProperties = null;
                this.parserPool = null;
                this.enabledScopeControls.Clear();
                this.resourceManager = null;
                this.runtimeDirectives = new Hashtable();
                this.runtimeDirectivesShared = null;
                this.uberSpect = null;
                this.stringInterning = false;
                this.parserConfiguration = new ParserConfiguration();

                /*
                 *  create a VM factory, introspector, and application attributes
                 */
                vmFactory = new VelocimacroFactory(this);

                /*
                 * and a store for the application attributes
                 */
                applicationAttributes = new Hashtable();
            }
        }

        /**
         * Returns true if the RuntimeInstance has been successfully initialized.
         * @return True if the RuntimeInstance has been successfully initialized.
         * @since 1.5
         */
        public bool isInitialized()
        {
            return initialized;
        }

        /**
         * Init or die! (with some log help, of course)
         */
        private void requireInitialization()
        {
            if (!initialized)
            {
                try
                {
                    init();
                }
                catch (Exception e)
                {
                    log.Error("Could not auto-initialize Velocity", e);
                    throw new SystemException("Velocity could not be initialized!", e);
                }
            }
        }

        /**
         *  Initialize runtime internal properties
         */
        private void initializeSelfProperties()
        {
            /* initialize string interning (defaults to false) */
            stringInterning = getBoolean(RUNTIME_STRING_INTERNING, true);

            /* initialize indentation mode (defaults to 'lines') */
            String im = getString(SPACE_GOBBLING, "lines");
            try
            {
                spaceGobbling = SpaceGobbling.valueOf(im.toUpperCase(Locale.ROOT));
            }
            catch (NotSupportedException nse)
            {
                spaceGobbling = SpaceGobbling.LINES;
            }

            /* init parser behavior */
            hyphenAllowedInIdentifiers = getBoolean(PARSER_HYPHEN_ALLOWED, false);
        }

        private char getConfiguredCharacter(String configKey, char defaultChar)
        {
            String configuredChar = getString(configKey);
            if (configuredChar != null)
            {
                if (configuredChar.Length != 1)
                {
                    throw new IllegalArgumentException(String.format("value of '%s' must be a single character string, but is '%s'", configKey, configuredChar));
                }
                return configuredChar.charAt(0);
            }
            return defaultChar;
        }

        /**
         *  Gets the classname for the Uberspect introspection package and
         *  instantiates an instance.
         */
        private void initializeIntrospection()
        {
            String[] uberspectors = configuration.getStringArray(RuntimeConstants.UBERSPECT_CLASSNAME);
            foreach (String rm in uberspectors)
            {
                Object o = null;

                try
                {
                    o = ClassUtils.getNewInstance(rm);
                }
                catch (ClassNotFoundException cnfe)
                {
                    String err = "The specified class for Uberspect (" + rm
                        + ") does not exist or is not accessible to the current classloader.";
                    log.Error(err);
                    throw new VelocityException(err, cnfe);
                }
                catch (InstantiationException ie)
                {
                    throw new VelocityException("Could not instantiate class '" + rm + "'", ie);
                }
                catch (IllegalAccessException ae)
                {
                    throw new VelocityException("Cannot access class '" + rm + "'", ae);
                }

                if (!(o is Uberspect))
                {
                    String err = "The specified class for Uberspect ("
                        + rm + ") does not implement " + nameof(Uberspect)
                        + "; Velocity is not initialized correctly.";

                    log.Error(err);
                    throw new VelocityException(err);
                }

                Uberspect u = (Uberspect)o;

                if (u is RuntimeServicesAware)
                {
                    ((RuntimeServicesAware)u).setRuntimeServices(this);
                }

                if (uberSpect == null)
                {
                    uberSpect = u;
                }
                else
                {
                    if (u is ChainableUberspector)
                    {
                        ((ChainableUberspector)u).wrap(uberSpect);
                        uberSpect = u;
                    }
                    else
                    {
                        uberSpect = new LinkingUberspector(uberSpect, u);
                    }
                }
            }

            if (uberSpect != null)
            {
                uberSpect.init();
            }
            else
            {
                /*
                 *  someone screwed up.  Lets not fool around...
                 */

                String err = "It appears that no class was specified as the"
                + " Uberspect.  Please ensure that all configuration"
                + " information is correct.";

                log.Error(err);
                throw new VelocityException(err);
            }
        }

        /**
         * Initializes the Velocity Runtime with properties file.
         * The properties file may be in the file system proper,
         * or the properties file may be in the classpath.
         */
        private void setDefaultProperties()
        {
            Stream inputStream = null;
            try
            {
                inputStream = getClass().getClassLoader()
                    .getResourceAsStream(DEFAULT_RUNTIME_PROPERTIES);

                if (inputStream == null)
                    throw new IOException("Resource not found: " + DEFAULT_RUNTIME_PROPERTIES);

                configuration.load(inputStream);

                /* populate 'defaultEncoding' member */
                defaultEncoding = getString(INPUT_ENCODING, ENCODING_DEFAULT);

                log.Debug("Default Properties resource: {}", DEFAULT_RUNTIME_PROPERTIES);
            }
            catch (IOException ioe)
            {
                String msg = "Cannot get Velocity Runtime default properties!";
                log.Error(msg, ioe);
                throw new SystemException(msg, ioe);
            }
            finally
            {
                try
                {
                    if (inputStream != null)
                    {
                        inputStream.close();
                    }
                }
                catch (IOException ioe)
                {
                    String msg = "Cannot close Velocity Runtime default properties!";
                    log.Error(msg, ioe);
                    throw new SystemException(msg, ioe);
                }
            }
        }

        /**
         * Allows an external system to set a property in
         * the Velocity Runtime.
         *
         * @param key property key
         * @param  value property value
         */
        public void setProperty(String key, Object value)
        {
            if (overridingProperties == null)
            {
                overridingProperties = new ExtProperties();
            }

            overridingProperties.setProperty(key, value);
        }


        /**
         * Add all properties contained in the file fileName to the RuntimeInstance properties
         * @param fileName
         */
        public void setProperties(String fileName)
        {
            ExtProperties props = null;
            try
            {
                props = new ExtProperties(fileName);
            }
            catch (IOException e)
            {
                throw new VelocityException("Error reading properties from '"
                  + fileName + "'", e);
            }

            Enumeration en = props.keys();
            while (en.hasMoreElements())
            {
                String key = en.nextElement().ToString();
                setProperty(key, props.get(key));
            }
        }


        /**
         * Add all the properties in props to the RuntimeInstance properties
         * @param props
         */
        public void setProperties(Hashtable props)
        {
            Enumeration en = props.keys();
            while (en.hasMoreElements())
            {
                String key = en.nextElement().ToString();
                setProperty(key, props.get(key));
            }
        }

        /**
         * Allow an external system to set an ExtProperties
         * object to use.
         *
         * @param  configuration
         * @since 2.0
         */
        public void setConfiguration(ExtProperties configuration)
        {
            if (overridingProperties == null)
            {
                overridingProperties = configuration;
            }
            else
            {
                // Avoid possible ConcurrentModificationException
                if (overridingProperties != configuration)
                {
                    overridingProperties.combine(configuration);
                }
            }
        }

        /**
         * Add a property to the configuration. If it already
         * exists then the value stated here will be added
         * to the configuration entry. For example, if
         *
         * resource.loader = file
         *
         * is already present in the configuration and you
         *
         * addProperty("resource.loader", "classpath")
         *
         * Then you will end up with a Vector like the
         * following:
         *
         * ["file", "classpath"]
         *
         * @param  key
         * @param  value
         */
        public void addProperty(String key, Object value)
        {
            if (overridingProperties == null)
            {
                overridingProperties = new ExtProperties();
            }

            overridingProperties.addProperty(key, value);
        }

        /**
         * Clear the values pertaining to a particular
         * property.
         *
         * @param key of property to clear
         */
        public void clearProperty(String key)
        {
            if (overridingProperties != null)
            {
                overridingProperties.clearProperty(key);
            }
        }

        /**
         *  Allows an external caller to get a property.  The calling
         *  routine is required to know the type, as this routine
         *  will return an Object, as that is what properties can be.
         *
         *  @param key property to return
         *  @return Value of the property or null if it does not exist.
         */
        public Object getProperty(String key)
        {
            Object o = null;

            /**
             * Before initialization, check the user-entered properties first.
             */
            if (!initialized && overridingProperties != null)
            {
                o = overridingProperties.get(key);
            }

            /**
             * After initialization, configuration will hold all properties.
             */
            if (o == null)
            {
                o = configuration.getProperty(key);
            }
            if (o is String)
            {
                return StringUtils.trim((String)o);
            }
            else
            {
                return o;
            }
        }

        /**
         * Initialize Velocity properties, if the default
         * properties have not been laid down first then
         * do so. Then proceed to process any overriding
         * properties. Laying down the default properties
         * gives a much greater chance of having a
         * working system.
         */
        private void initializeProperties()
        {
            /*
             * Always lay down the default properties first as
             * to provide a solid base.
             */
            if (configuration.isInitialized() == false)
            {
                setDefaultProperties();
            }

            if (overridingProperties != null)
            {
                configuration.combine(overridingProperties);
            }
        }

        /**
         * Initialize the Velocity Runtime with a Properties
         * object.
         *
         * @param p Velocity properties for initialization
         */
        public void init(Hashtable p)
        {
            setConfiguration(ExtProperties.convertProperties(p));
            init();
        }

        /**
         * Initialize the Velocity Runtime with a
         * properties file path.
         *
         * @param configurationFile
         */
        public void init(String configurationFile)
        {
            setProperties(configurationFile);
            init();
        }

        private void initializeResourceManager()
        {
            /*
             * Which resource manager?
             */
            Object inst = getProperty(RuntimeConstants.RESOURCE_MANAGER_INSTANCE);
            String rm = getString(RuntimeConstants.RESOURCE_MANAGER_CLASS);

            if (inst != null)
            {
                if (typeof(ResourceManager).IsAssignableFrom(inst.GetType()))
                {
                    resourceManager = (ResourceManager)inst;
                    resourceManager.initialize(this);
                }
                else
                {
                    String msg = inst.GetType().Name + " object set as resource.manager.instance is not a valid org.apache.velocity.runtime.resource.ResourceManager.";
                    log.Error(msg);
                    throw new VelocityException(msg);
                }
            }
            else if (rm != null && rm.Length > 0)
            {
                /*
                 *  if something was specified, then make one.
                 *  if that isn't a ResourceManager, consider
                 *  this a huge error and throw
                 */

                Object o = null;

                try
                {
                    o = ClassUtils.getNewInstance(rm);
                }
                catch (ClassNotFoundException cnfe)
                {
                    String err = "The specified class for ResourceManager (" + rm
                        + ") does not exist or is not accessible to the current classloader.";
                    log.Error(err);
                    throw new VelocityException(err, cnfe);
                }
                catch (InstantiationException ie)
                {
                    throw new VelocityException("Could not instantiate class '" + rm + "'", ie);
                }
                catch (IllegalAccessException ae)
                {
                    throw new VelocityException("Cannot access class '" + rm + "'", ae);
                }

                if (!(o is ResourceManager))
                {
                    String err = "The specified class for ResourceManager (" + rm
                        + ") does not implement " + nameof(ResourceManager)
                        + "; Velocity is not initialized correctly.";

                    log.Error(err);
                    throw new VelocityException(err);
                }

                resourceManager = (ResourceManager)o;
                resourceManager.initialize(this);
                setProperty(RESOURCE_MANAGER_INSTANCE, resourceManager);
            }
            else
            {
                /*
                 *  someone screwed up.  Lets not fool around...
                 */

                String err = "It appears that no class or instance was specified as the"
                + " ResourceManager.  Please ensure that all configuration"
                + " information is correct.";

                log.Error(err);
                throw new VelocityException(err);
            }
        }

        private void initializeEventHandlers()
        {

            eventCartridge = new EventCartridge();
            eventCartridge.setRuntimeServices(this);

            /**
             * For each type of event handler, get the class name, instantiate it, and store it.
             */

            String[] referenceinsertion = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION);
            if (referenceinsertion != null)
            {
                foreach (String aReferenceinsertion in referenceinsertion)
                {
                    EventHandler ev = initializeSpecificEventHandler(aReferenceinsertion, RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION, typeof(ReferenceInsertionEventHandler));
                    if (ev != null)
                        eventCartridge.addReferenceInsertionEventHandler((ReferenceInsertionEventHandler)ev);
                }
            }

            String[] methodexception = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION);
            if (methodexception != null)
            {
                foreach (String aMethodexception in methodexception)
                {
                    EventHandler ev = initializeSpecificEventHandler(aMethodexception, RuntimeConstants.EVENTHANDLER_METHODEXCEPTION, typeof(MethodExceptionEventHandler));
                    if (ev != null)
                        eventCartridge.addMethodExceptionHandler((MethodExceptionEventHandler)ev);
                }
            }

            String[] includeHandler = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_INCLUDE);
            if (includeHandler != null)
            {
                foreach (String anIncludeHandler in includeHandler)
                {
                    EventHandler ev = initializeSpecificEventHandler(anIncludeHandler, RuntimeConstants.EVENTHANDLER_INCLUDE, typeof(IncludeEventHandler));
                    if (ev != null)
                        eventCartridge.addIncludeEventHandler((IncludeEventHandler)ev);
                }
            }

            String[] invalidReferenceSet = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES);
            if (invalidReferenceSet != null)
            {
                foreach (String anInvalidReferenceSet in invalidReferenceSet)
                {
                    EventHandler ev = initializeSpecificEventHandler(anInvalidReferenceSet, RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES, typeof(InvalidReferenceEventHandler));
                    if (ev != null)
                    {
                        eventCartridge.addInvalidReferenceEventHandler((InvalidReferenceEventHandler)ev);
                    }
                }
            }


        }

        private EventHandler initializeSpecificEventHandler(String classname, String paramName, Type EventHandlerInterface)
        {
            if (classname != null && classname.Length > 0)
            {
                Object o = null;
                try
                {
                    o = ClassUtils.getNewInstance(classname);
                }
                catch (ClassNotFoundException cnfe)
                {
                    String err = "The specified class for "
                        + paramName + " (" + classname
                        + ") does not exist or is not accessible to the current classloader.";
                    log.Error(err);
                    throw new VelocityException(err, cnfe);
                }
                catch (InstantiationException ie)
                {
                    throw new VelocityException("Could not instantiate class '" + classname + "'", ie);
                }
                catch (IllegalAccessException ae)
                {
                    throw new VelocityException("Cannot access class '" + classname + "'", ae);
                }

                if (!EventHandlerInterface.isAssignableFrom(EventHandlerInterface))
                {
                    String err = "The specified class for " + paramName + " ("
                        + classname + ") does not implement "
                        + EventHandlerInterface.getName()
                        + "; Velocity is not initialized correctly.";

                    log.Error(err);
                    throw new VelocityException(err);
                }

                EventHandler ev = (EventHandler)o;
                if (ev is RuntimeServicesAware)
                    ((RuntimeServicesAware)ev).setRuntimeServices(this);
                return ev;

            }
            else
                return null;
        }

        /**
         * Initialize the Velocity logging system.
         */
        private void initializeLog()
        {
            // if we were provided a specific logger or logger name, let's use it
            try
            {
                /* If a Logger instance was set as a configuration
                 * value, use that.  This is any class the user specifies.
                 */
                Object o = getProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE);
                if (o != null)
                {
                    // check for a Logger
                    if (typeof(ILog).IsAssignableFrom(o.getClass()))
                    {
                        //looks ok
                        log = (Logger)o;
                    }
                    else
                    {
                        String msg = o.GetType().Name + " object set as runtime.log.instance is not a valid org.slf4j.Logger implementation.";
                        log.Error(msg);
                        throw new VelocityException(msg);
                    }
                }
                else
                {
                    /* otherwise, see if a logger name was specified.
                     */
                    o = getProperty(RuntimeConstants.RUNTIME_LOG_NAME);
                    if (o != null)
                    {
                        if (o is String)
                        {
                            log = LoggerFactory.getLogger((String)o);
                        }
                        else
                        {
                            String msg = o.GetType().Name + " object set as runtime.log.name is not a valid string.";
                            log.Error(msg);
                            throw new VelocityException(msg);
                        }
                    }
                }
                /* else keep our default Velocity logger
                 */

                /* Initialize LogContext */
                bool trackLocation = getBoolean(RUNTIME_LOG_TRACK_LOCATION, false);
                logContext = new LogContext(trackLocation);
            }
            catch (Exception e)
            {
                throw new VelocityException("Error initializing log: " + e.getMessage(), e);
            }
        }


        /**
         * This methods initializes all the directives
         * that are used by the Velocity Runtime. The
         * directives to be initialized are listed in
         * the RUNTIME_DEFAULT_DIRECTIVES properties
         * file.
         */
        private void initializeDirectives()
        {
            Hashtable directiveProperties = new Hashtable();

            /*
             * Grab the properties file with the list of directives
             * that we should initialize.
             */

            InputStream inputStream = null;

            try
            {
                inputStream = getClass().getResourceAsStream('/' + DEFAULT_RUNTIME_DIRECTIVES);

                if (inputStream == null)
                {
                    throw new VelocityException("Error loading directive.properties! " +
                                        "Something is very wrong if these properties " +
                                        "aren't being located. Either your Velocity " +
                                        "distribution is incomplete or your Velocity " +
                                        "jar file is corrupted!");
                }

                directiveProperties.load(inputStream);

            }
            catch (IOException ioe)
            {
                String msg = "Error while loading directive properties!";
                log.Error(msg, ioe);
                throw new SystemException(msg, ioe);
            }
            finally
            {
                try
                {
                    if (inputStream != null)
                    {
                        inputStream.close();
                    }
                }
                catch (IOException ioe)
                {
                    String msg = "Cannot close directive properties!";
                    log.Error(msg, ioe);
                    throw new SystemException(msg, ioe);
                }
            }


            /*
             * Grab all the values of the properties. These
             * are all class names for example:
             *
             * org.apache.velocity.runtime.directive.Foreach
             */
            Enumeration directiveClasses = directiveProperties.elements();

            while (directiveClasses.hasMoreElements())
            {
                String directiveClass = (String)directiveClasses.nextElement();
                loadDirective(directiveClass);
                log.Debug("Loaded System Directive: {}", directiveClass);
            }

            /*
             *  now the user's directives
             */

            String[] userdirective = configuration.getStringArray(CUSTOM_DIRECTIVES);

            foreach (String anUserdirective in userdirective)
            {
                loadDirective(anUserdirective);
                log.Debug("Loaded User Directive: {}", anUserdirective);
            }

        }

        /**
         * Programatically add a directive.
         * @param directive
         */
        public void addDirective(Directive directive)
        {
            lock (this)
            {
                runtimeDirectives.put(directive.getName(), directive);
                updateSharedDirectivesMap();
            }
        }

        /**
         * Retrieve a previously instantiated directive.
         * @param name name of the directive
         * @return the {@link Directive} for that name
         */
        public Directive getDirective(String name)
        {
            return (Directive)runtimeDirectivesShared.get(name);
        }

        /**
         * Remove a directive.
         * @param name name of the directive.
         */
        public void removeDirective(String name)
        {
            lock (this)
            {
                runtimeDirectives.remove(name);
                updateSharedDirectivesMap();
            }
        }

        /**
         * Makes an unsynchronized copy of the directives map
         * that is used for Directive lookups by all parsers.
         *
         * This follows Copy-on-Write pattern. The cost of creating
         * a new map is acceptable since directives are typically
         * set and modified only during Velocity setup phase.
         */
        private void updateSharedDirectivesMap()
        {
            runtimeDirectivesShared = new HashMap(runtimeDirectives);
        }

        /**
         *  instantiates and loads the directive with some basic checks
         *
         *  @param directiveClass classname of directive to load
         */
        public void loadDirective(String directiveClass)
        {
            try
            {
                Object o = ClassUtils.getNewInstance(directiveClass);

                if (o is Directive)
                {
                    Directive directive = (Directive)o;
                    addDirective(directive);
                }
                else
                {
                    String msg = directiveClass + " does not implement "
                        + nameof(Directive) + "; it cannot be loaded.";
                    log.Error(msg);
                    throw new VelocityException(msg);
                }
            }
            // The ugly threesome:  ClassNotFoundException,
            // IllegalAccessException, InstantiationException.
            // Ignore Findbugs complaint for now.
            catch (Exception e)
            {
                String msg = "Failed to load Directive: " + directiveClass;
                log.Error(msg, e);
                throw new VelocityException(msg, e);
            }
        }


        /**
         * Initializes the Velocity parser pool.
         */
        private void initializeParserPool()
        {
            /*
             * First initialize parser class. If it's not valid or not found, it will generate an error
             * later on in this method when parser creation is tester.
             */
            String parserClassName = getString(PARSER_CLASS, DEFAULT_PARSER_CLASS);
            Class parserClass;
            try
            {
                parserClass = ClassUtils.getClass(parserClassName);
            }
            catch (ClassNotFoundException cnfe)
            {
                throw new VelocityException("parser class not found: " + parserClassName, cnfe);
            }
            try
            {
                parserConstructor = parserClass.getConstructor(typeof(RuntimeServices));
            }
            catch (NoSuchMethodException nsme)
            {
                throw new VelocityException("parser class must provide a constructor taking a RuntimeServices argument", nsme);
            }

            /*
             * Which parser pool?
             */
            String pp = getString(RuntimeConstants.PARSER_POOL_CLASS);

            if (pp != null && pp.Length > 0)
            {
                /*
                 *  if something was specified, then make one.
                 *  if that isn't a ParserPool, consider
                 *  this a huge error and throw
                 */

                Object o = null;

                try
                {
                    o = ClassUtils.getNewInstance(pp);
                }
                catch (ClassNotFoundException cnfe)
                {
                    String err = "The specified class for ParserPool ("
                        + pp
                        + ") does not exist (or is not accessible to the current classloader.";
                    log.Error(err);
                    throw new VelocityException(err, cnfe);
                }
                catch (InstantiationException ie)
                {
                    throw new VelocityException("Could not instantiate class '" + pp + "'", ie);
                }
                catch (IllegalAccessException ae)
                {
                    throw new VelocityException("Cannot access class '" + pp + "'", ae);
                }

                if (!(o is ParserPool))
                {
                    String err = "The specified class for ParserPool ("
                        + pp + ") does not implement " + typeof(ParserPool)
                            + " Velocity not initialized correctly.";

                    log.Error(err);
                    throw new VelocityException(err);
                }

                parserPool = (ParserPool)o;

                parserPool.initialize(this);

                /*
                 * test parser creation and use generated parser to fill up customized characters
                 */
                Parser parser = parserPool.get();
                parserConfiguration = new ParserConfiguration();
                parserConfiguration.setDollarChar(parser.dollar());
                parserConfiguration.setHashChar(parser.hash());
                parserConfiguration.setAtChar(parser.at());
                parserConfiguration.setAsteriskChar(parser.asterisk());
                parserPool.put(parser);
            }
            else
            {
                /*
                 *  someone screwed up.  Lets not fool around...
                 */

                String err = "It appears that no class was specified as the"
                    + " ParserPool.  Please ensure that all configuration"
                    + " information is correct.";

                log.Error(err);
                throw new VelocityException(err);
            }

        }

        /**
         * Returns a JavaCC generated Parser.
         *
         * @return Parser javacc generated parser
         */
        public Parser createNewParser()
        {
            requireInitialization();
            try
            {
                return (Parser)parserConstructor.newInstance((RuntimeServices)this);
            }
            catch (Exception e)
            {
                throw new VelocityException("could not build new parser class", e);
            }
        }

        /**
         * Parse the input and return the root of
         * AST node structure.
         * <br><br>
         *  In the event that it runs out of parsers in the
         *  pool, it will create and let them be GC'd
         *  dynamically, logging that it has to do that.  This
         *  is considered an exceptional condition.  It is
         *  expected that the user will set the
         *  PARSER_POOL_SIZE property appropriately for their
         *  application.  We will revisit this.
         *
         * @param reader Reader retrieved by a resource loader
         * @param template template being parsed
         * @return A root node representing the template as an AST tree.
         * @throws ParseException When the template could not be parsed.
         */
        public SimpleNode parse(TextReader reader, Template template)
        {
            requireInitialization();

            Parser parser = parserPool.get();
            bool keepParser = true;
            if (parser == null)
            {
                /*
                 *  if we couldn't get a parser from the pool make one and log it.
                 */
                log.info("Runtime: ran out of parsers. Creating a new one. "
                         + " Please increment the parser.pool.size property."
                         + " The current value is too small.");
                parser = createNewParser();
                keepParser = false;
            }

            try
            {
                return parser.parse(reader, template);
            }
            finally
            {
                if (keepParser)
                {
                    /* drop the parser Template reference to allow garbage collection */
                    parser.resetCurrentTemplate();
                    parserPool.put(parser);
                }

            }
        }

        private void initializeScopeSettings()
        {
            ExtProperties scopes = configuration.subset(CONTEXT_SCOPE_CONTROL);
            if (scopes != null)
            {
                Iterator<String> scopeIterator = scopes.getKeys();
                while (scopeIterator.hasNext())
                {
                    String scope = scopeIterator.next();
                    bool enabled = scopes.getBoolean(scope);
                    if (enabled) enabledScopeControls.Add(scope);
                }
            }
        }

        /**
         * Renders the input string using the context into the output writer.
         * To be used when a template is dynamically constructed, or want to use
         * Velocity as a token replacer.
         * <br>
         * Note! Macros defined in evaluate() calls are not persisted in memory so next evaluate() call
         * does not know about macros defined during previous calls.
         *
         * @param context context to use in rendering input string
         * @param out  Writer in which to render the output
         * @param logTag  string to be used as the template name for log
         *                messages in case of error
         * @param instring input string containing the VTL to be rendered
         *
         * @return true if successful, false otherwise.  If false, see
         *              Velocity runtime log
         * @throws ParseErrorException The template could not be parsed.
         * @throws MethodInvocationException A method on a context object could not be invoked.
         * @throws ResourceNotFoundException A referenced resource could not be loaded.
         * @since Velocity 1.6
         */
        public bool evaluate(Context context, TextWriter _out,
                                String logTag, String instring)
        {
            return evaluate(context, _out, logTag, new StringReader(instring));
        }

        /**
         * Renders the input reader using the context into the output writer.
         * To be used when a template is dynamically constructed, or want to
         * use Velocity as a token replacer.
         * <br>
         * Note! Macros defined in evaluate() calls are not persisted in memory so next evaluate() call
         * does not know about macros defined during previous calls.
         *
         * @param context context to use in rendering input string
         * @param writer  Writer in which to render the output
         * @param logTag  string to be used as the template name for log messages
         *                in case of error
         * @param reader Reader containing the VTL to be rendered
         *
         * @return true if successful, false otherwise.  If false, see
         *              Velocity runtime log
         * @throws ParseErrorException The template could not be parsed.
         * @throws MethodInvocationException A method on a context object could not be invoked.
         * @throws ResourceNotFoundException A referenced resource could not be loaded.
         * @since Velocity 1.6
         */
        public bool evaluate(Context context, TextWriter writer,
                                String logTag, TextReader reader)
        {
            if (logTag == null)
            {
                throw new NullReferenceException("logTag (i.e. template name) cannot be null, you must provide an identifier for the content being evaluated");
            }

            SimpleNode nodeTree = null;
            Template t = new Template();
            t.setName(logTag);
            try
            {
                nodeTree = parse(reader, t);
            }
            catch (ParseException pex)
            {
                throw new ParseErrorException(pex, null);
            }
            catch (TemplateInitException pex)
            {
                throw new ParseErrorException(pex, null);
            }

            if (nodeTree == null)
            {
                return false;
            }
            else
            {
                return render(context, writer, logTag, nodeTree);
            }
        }


        /**
         * Initializes and renders the AST {@link SimpleNode} using the context
         * into the output writer.
         *
         * @param context context to use in rendering input string
         * @param writer  Writer in which to render the output
         * @param logTag  string to be used as the template name for log messages
         *                in case of error
         * @param nodeTree SimpleNode which is the root of the AST to be rendered
         *
         * @return true if successful, false otherwise.  If false, see
         *              Velocity runtime log for errors
         * @throws ParseErrorException The template could not be parsed.
         * @throws MethodInvocationException A method on a context object could not be invoked.
         * @throws ResourceNotFoundException A referenced resource could not be loaded.
         * @since Velocity 1.6
         */
        public bool render(Context context, TextWriter writer,
                              String logTag, SimpleNode nodeTree)
        {
            /*
             * we want to init then render
             */
            InternalContextAdapterImpl ica =
                new InternalContextAdapterImpl(context);

            ica.pushCurrentTemplateName(logTag);

            try
            {
                try
                {
                    nodeTree.init(ica, this);
                }
                catch (TemplateInitException pex)
                {
                    throw new ParseErrorException(pex, null);
                }
                /**
                 * pass through application level runtime exceptions
                 */
                catch (SystemException e)
                {
                    throw e;
                }
                catch (Exception e)
                {
                    String msg = "RuntimeInstance.render(): init exception for tag = " + logTag;
                    log.Error(msg, e);
                    throw new VelocityException(msg, e, getLogContext().getStackTrace());
                }

                try
                {
                    if (isScopeControlEnabled(evaluateScopeName))
                    {
                        Object previous = ica.get(evaluateScopeName);
                        context.put(evaluateScopeName, new Scope(this, previous));
                    }
                    /**
                     * optionally put the context in itself if asked so
                     */
                    String self = getString(CONTEXT_AUTOREFERENCE_KEY);
                    if (self != null) context.put(self, context);
                    nodeTree.render(ica, writer);
                }
                catch (StopCommand stop)
                {
                    if (!stop.isFor(this))
                    {
                        throw stop;
                    }
                    else
                    {
                        log.Debug(stop.getMessage());
                    }
                }
                catch (IOException e)
                {
                    throw new VelocityException("IO Error in writer: " + e.getMessage(), e, getLogContext().getStackTrace());
                }
            }
            finally
            {
                ica.popCurrentTemplateName();
                if (isScopeControlEnabled(evaluateScopeName))
                {
                    Object obj = ica.get(evaluateScopeName);
                    if (obj is Scope)
                    {
                        Scope scope = (Scope)obj;
                        if (scope.getParent() != null)
                        {
                            ica.put(evaluateScopeName, scope.getParent());
                        }
                        else if (scope.getReplaced() != null)
                        {
                            ica.put(evaluateScopeName, scope.getReplaced());
                        }
                        else
                        {
                            ica.remove(evaluateScopeName);
                        }
                    }
                }
            }

            return true;
        }

        /**
         * Invokes a currently registered Velocimacro with the _params provided
         * and places the rendered stream into the writer.
         * <br>
         * Note: currently only accepts args to the VM if they are in the context.
         * <br>
         * Note: only macros in the global context can be called. This method doesn't find macros defined by
         * templates during previous mergeTemplate calls if Velocity.VM_PERM_INLINE_LOCAL has been enabled.
         *
         * @param vmName name of Velocimacro to call
         * @param logTag string to be used for template name in case of error. if null,
         *               the vmName will be used
         * @param _params keys for args used to invoke Velocimacro, in java format
         *               rather than VTL (eg  "foo" or "bar" rather than "$foo" or "$bar")
         * @param context Context object containing data/objects used for rendering.
         * @param writer  Writer for output stream
         * @return true if Velocimacro exists and successfully invoked, false otherwise.
         * @since 1.6
         */
        public bool invokeVelocimacro(String vmName, String logTag,
                                         String[] _params, Context context,
                                         TextWriter writer)
        {
            /* check necessary parameters */
            if (vmName == null || context == null || writer == null)
            {
                String msg = "RuntimeInstance.invokeVelocimacro(): invalid call: vmName, context, and writer must not be null";
                log.Error(msg);
                throw new NullPointerException(msg);
            }

            /* handle easily corrected parameters */
            if (logTag == null)
            {
                logTag = vmName;
            }
            if (_params == null)
            {
                _params = new String[0];
            }

            /* does the VM exist? (only global scope is scanned so this doesn't find inline macros in templates) */
            if (!isVelocimacro(vmName, null))
            {
                String msg = "RuntimeInstance.invokeVelocimacro(): VM '" + vmName
                             + "' is not registered.";
                log.Error(msg);
                throw new VelocityException(msg, null, getLogContext().getStackTrace());
            }

            /* now just create the VM call, and use evaluate */
            StringBuilder template = new StringBuilder(String.valueOf(parserConfiguration.getHashChar()));
            template.Append(vmName);
            template.Append("(");
            foreach (String param in _params)
            {
                template.Append(" $");
                template.Append(param);
            }
            template.Append(" )");

            return evaluate(context, writer, logTag, template.ToString());
        }

        /**
         * Retrieves and caches the configured default encoding
         * for better performance. (VELOCITY-606)
         */
        private String getDefaultEncoding()
        {
            return defaultEncoding;
        }

        /**
         * Returns a <code>Template</code> from the resource manager.
         * This method assumes that the character encoding of the
         * template is set by the <code>resource.default_encoding</code>
         * property. The default is UTF-8.
         *
         * @param name The file name of the desired template.
         * @return     The template.
         * @throws ResourceNotFoundException if template not found
         *          from any available source.
         * @throws ParseErrorException if template cannot be parsed due
         *          to syntax (or other) error.
         */
        public Template getTemplate(String name)
        {
            return getTemplate(name, null);
        }

        /**
         * Returns a <code>Template</code> from the resource manager
         *
         * @param name The  name of the desired template.
         * @param encoding Character encoding of the template
         * @return     The template.
         * @throws ResourceNotFoundException if template not found
         *          from any available source.
         * @throws ParseErrorException if template cannot be parsed due
         *          to syntax (or other) error.
         */
        public Template getTemplate(String name, String encoding)
        {
            requireInitialization();
            if (encoding == null) encoding = getDefaultEncoding();
            return (Template)
                    resourceManager.getResource(name,
                        ResourceManager.RESOURCE_TEMPLATE, encoding);
        }

        /**
         * Returns a static content resource from the
         * resource manager.  Uses the current value
         * if INPUT_ENCODING as the character encoding.
         *
         * @param name Name of content resource to get
         * @return parsed ContentResource object ready for use
         * @throws ResourceNotFoundException if template not found
         *          from any available source.
         * @throws ParseErrorException When the template could not be parsed.
         */
        public ContentResource getContent(String name)
        {
            /*
             *  the encoding is irrelvant as we don't do any converstion
             *  the bytestream should be dumped to the output stream
             */

            return getContent(name, getDefaultEncoding());
        }

        /**
         * Returns a static content resource from the
         * resource manager.
         *
         * @param name Name of content resource to get
         * @param encoding Character encoding to use
         * @return parsed ContentResource object ready for use
         * @throws ResourceNotFoundException if template not found
         *          from any available source.
         * @throws ParseErrorException When the template could not be parsed.
         */
        public ContentResource getContent(String name, String encoding)
        {
            requireInitialization();

            return (ContentResource)
                    resourceManager.getResource(name,
                            ResourceManager.RESOURCE_CONTENT, encoding);
        }


        /**
         *  Determines if a template exists and returns name of the loader that
         *  provides it.  This is a slightly less hokey way to support
         *  the Velocity.resourceExists() utility method, which was broken
         *  when per-template encoding was introduced.  We can revisit this.
         *
         *  @param resourceName Name of template or content resource
         *  @return class name of loader than can provide it
         */
        public String getLoaderNameForResource(String resourceName)
        {
            requireInitialization();

            return resourceManager.getLoaderNameForResource(resourceName);
        }

        /**
         * Returns the configured logger.
         *
         * @return A Logger instance
         * @since 1.5
         */
        public ILog getLog()
        {
            return log;
        }

        /**
         * Get a logger for the specified child namespace.
         * If a logger was configured using the runtime.log.instance configuration property, returns this instance.
         * Otherwise, uses SLF4J LoggerFactory on baseNamespace '.' childNamespace.
         * @param childNamespace
         * @return child namespace logger
         */
        public Logger getLog(String childNamespace)
        {
            ILog log = (Logger)getProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE);
            if (log == null)
            {
                String loggerName = getString(RUNTIME_LOG_NAME, DEFAULT_RUNTIME_LOG_NAME) + "." + childNamespace;
                log = LoggerFactory.getLogger(loggerName);
            }
            return log;
        }

        /**
         * Get the LogContext object used to tack locations in templates.
         * @return LogContext object
         * @since 2.2
         */
        public LogContext getLogContext()
        {
            return logContext;
        }

        /**
         * String property accessor method with default to hide the
         * configuration implementation.
         *
         * @param key property key
         * @param defaultValue  default value to return if key not
         *               found in resource manager.
         * @return value of key or default
         */
        public String getString(String key, String defaultValue)
        {
            return configuration.getString(key, defaultValue);
        }

        /**
         * Returns the appropriate VelocimacroProxy object if vmName
         * is a valid current Velocimacro.
         *
         * @param vmName  Name of velocimacro requested
         * @param renderingTemplate Template we are currently rendering. This
         *    information is needed when VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL setting is true
         *    and template contains a macro with the same name as the global macro library.
         * @param template Template which acts as the host for the macro
         *
         * @return VelocimacroProxy
         */
        public Directive getVelocimacro(String vmName, Template renderingTemplate, Template template)
        {
            return vmFactory.getVelocimacro(vmName, renderingTemplate, template);
        }

        /**
         * Adds a new Velocimacro. Usually called by Macro only while parsing.
         *
         * @param name  Name of velocimacro
         * @param macro  root AST node of the parsed macro
         * @param macroArgs  Array of macro arguments, containing the
         *        #macro() arguments and default values.  the 0th is the name.
         * @param definingTemplate Template containing the source of the macro
         *
         * @return bool  True if added, false if rejected for some
         *                  reason (either parameters or permission settings)
         */
        public bool addVelocimacro(String name,
                                       Node macro,
                                       List<Macro.MacroArg> macroArgs,
                                       Template definingTemplate)
        {
            return vmFactory.addVelocimacro(stringInterning ? name.intern() : name, macro, macroArgs, definingTemplate);
        }

        /**
         *  Checks to see if a VM exists
         *
         * @param vmName Name of the Velocimacro.
         * @param template Template on which to look for the Macro.
         * @return True if VM by that name exists, false if not
         */
        public bool isVelocimacro(String vmName, Template template)
        {
            return vmFactory.isVelocimacro(stringInterning ? vmName.intern() : vmName, template);
        }

        /* --------------------------------------------------------------------
         * R U N T I M E  A C C E S S O R  M E T H O D S
         * --------------------------------------------------------------------
         * These are the getXXX() methods that are a simple wrapper
         * around the configuration object. This is an attempt
         * to make a the Velocity Runtime the single access point
         * for all things Velocity, and allow the Runtime to
         * adhere as closely as possible the the Mediator pattern
         * which is the ultimate goal.
         * --------------------------------------------------------------------
         */

        /**
         * String property accessor method to hide the configuration implementation
         * @param key  property key
         * @return   value of key or null
         */
        public String getString(String key)
        {
            return StringUtils.trim(configuration.getString(key));
        }

        /**
         * Int property accessor method to hide the configuration implementation.
         *
         * @param key Property key
         * @return value
         */
        public int getInt(String key)
        {
            return configuration.getInt(key);
        }

        /**
         * Int property accessor method to hide the configuration implementation.
         *
         * @param key  property key
         * @param defaultValue The default value.
         * @return value
         */
        public int getInt(String key, int defaultValue)
        {
            return configuration.getInt(key, defaultValue);
        }

        /**
         * Boolean property accessor method to hide the configuration implementation.
         *
         * @param key property key
         * @param def The default value if property not found.
         * @return value of key or default value
         */
        public bool getBoolean(String key, bool def)
        {
            return configuration.getBoolean(key, def);
        }

        /**
         * Return the velocity runtime configuration object.
         *
         * @return Configuration object which houses the Velocity runtime
         * properties.
         */
        public ExtProperties getConfiguration()
        {
            return configuration;
        }

        /**
         * Returns the event handlers for the application.
         * @return The event handlers for the application.
         * @since 1.5
         */
        public EventCartridge getApplicationEventCartridge()
        {
            return eventCartridge;
        }


        /**
         *  Gets the application attribute for the given key
         *
         * @param key
         * @return The application attribute for the given key.
         */
        public Object getApplicationAttribute(Object key)
        {
            return applicationAttributes.get(key);
        }

        /**
         *   Sets the application attribute for the given key
         *
         * @param key
         * @param o The new application attribute.
         * @return The old value of this attribute or null if it hasn't been set before.
         */
        public Object setApplicationAttribute(Object key, Object o)
        {
            return applicationAttributes.put(key, o);
        }

        /**
         * Returns the Uberspect object for this Instance.
         *
         * @return The Uberspect object for this Instance.
         */
        public Uberspect getUberspect()
        {
            return uberSpect;
        }

        /**
         * Whether to use string interning
         *
         * @return bool
         */
        public bool useStringInterning()
        {
            return stringInterning;
        }

        /**
         * get space gobbling mode
         * @return indentation mode
         */
        public SpaceGobbling getSpaceGobbling()
        {
            return spaceGobbling;
        }

        /**
         * get whether hyphens are allowed in identifiers
         * @return configured bool flag
         */
        public bool isHyphenAllowedInIdentifiers()
        {
            return hyphenAllowedInIdentifiers;
        }

        /**
         * Get whether to provide a scope control object for this scope
         * @param scopeName
         * @return scope control enabled
         * @since 2.1
         */
        public bool isScopeControlEnabled(String scopeName)
        {
            return enabledScopeControls.Contains(scopeName);
        }


        public override ParserConfiguration getParserConfiguration()
        {
            return parserConfiguration;
        }
    }
}